import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'dart:developer' as dev;

import 'peer.dart';
import 'udp_sender.dart';

class PeerServer {
  static const maxSentPeers = 30;
  final int port;
  final dynamic host;
  List<Peer> peers;
  late RawDatagramSocket socket;
  Peer? self;
  bool _running = false;
  get running => _running;
  UdpSender sender;
  late Timer refresher;

  PeerServer(this.port,
      {List<Peer>? initPeers,
      host,
      int ttl = 32,
      Duration refreshDuration = const Duration(seconds: 8),
      // When false,you need start it manmally by [run] .
      bool startOnCreate = true})
      : peers = initPeers ?? [],
        sender = UdpSender(usePort: port, ttl: ttl),
        host = host ?? InternetAddress.anyIPv4 {
    refresher = Timer.periodic(refreshDuration, refresh);
    if (startOnCreate) run();
  }

  run() async {
    if (_running) return;
    self = Peer(await getLocalAddress(), port);
    debug("self:$self");
    socket = await RawDatagramSocket.bind(host, port);
    debug("listen:$host:$port");
    _running = true;
    socket.listen((RawSocketEvent event) {
      if (event == RawSocketEvent.read) {
        var dg = socket.receive();
        if (dg != null) {
          try {
            handle(dg);
          } catch (e) {
            debug("UDPServer Error", error: e);
          }
        }
      }
    });
    for (var p in peers) {
      sendPeersTo(p, action: MyAction.online);
    }
  }

  handle(Datagram datagram) {
    var msg = MyMessage.fromMap(decode(datagram.data) as Map);
    debug("data:$msg");
    if (msg.action == MyAction.callback || msg.action == MyAction.online) {
      sendPeersTo(msg.self);
    }
    if (msg.action == MyAction.offline) {
      discardPeer(msg.self);
    } else {
      addPeers(toStart: [msg.self], toEnd: msg.peers);
    }
  }

  static dynamic decode(List<int> encoded) =>
      json.decode(utf8.decode(gzip.decode(encoded)));

  static List<int> encode(Object value) =>
      gzip.encode(utf8.encode(json.encode(value)));

  discardPeer(Peer peer) {
    peers.removeWhere((element) => element == peer);
  }

  addPeers({Iterable<Peer>? toStart, Iterable<Peer>? toEnd}) {
    peers.insertAll(0, toStart ?? []);
    peers.addAll(toEnd ?? []);
    var ps = peers.toSet();
    ps.remove(self);
    peers = ps.toList();
  }

  Future<int> sendPeersTo(Peer remote,
      {MyAction action = MyAction.none}) async {
    var msg =
        MyMessage(self: self!, peers: peers.take(maxSentPeers), action: action);
    debug("send:[$action]$remote");
    InternetAddress addr = remote.address ?? await remote.lookup!;
    int port = remote.port;
    return sender.send(encode(msg), addr, port);
  }

  refresh(Timer timer) {
    if (!_running) return;
    debug("refresh:$peers");
    if (peers.length <= 1) return;
    //滚动刷新
    var p = peers.last;
    peers.removeLast();
    sendPeersTo(p, action: MyAction.callback);
  }

  close() {
    if (!_running) return;
    refresher.cancel();
    _running = false;
    for (var p in peers) {
      sendPeersTo(p, action: MyAction.offline);
    }
    Future.delayed(Duration(seconds: 1), () => socket.close());
  }
}

enum MyAction { none, callback, offline, online }

class MyMessage {
  MyAction action = MyAction.none;
  late Peer self;
  List<Peer> peers = [];
  MyMessage(
      {required this.self,
      this.action = MyAction.none,
      Iterable<Peer>? peers}) {
    if (peers != null) {
      this.peers.addAll(peers);
    }
  }
  MyMessage.fromMap(Map map) {
    if (map.containsKey("action")) {
      switch (map["action"]) {
        case "callback":
          action = MyAction.callback;
          break;
        case "offline":
          action = MyAction.offline;
          break;
        case "online":
          action = MyAction.online;
      }
    }
    if (map.containsKey("self")) {
      self = Peer.parse(map["self"]);
    }
    if (map.containsKey("peers")) {
      peers.addAll((map["peers"] as List).map((e) => Peer.parse(e)));
    }
  }

  Map<String, dynamic> toJson() => {
        "self": self.toString(),
        "action": action.name,
        "peers": peers.map((e) => e.toString()).toList()
      };

  Map<String, dynamic> toMap() => toJson();

  @override
  String toString() => toJson().toString();
}

Future<InternetAddress> getLocalAddress({bool ipv6 = false}) async {
  var interfaceList = await NetworkInterface.list();
  for (NetworkInterface interface in interfaceList) {
    for (InternetAddress addr in interface.addresses) {
      if (addr.type == InternetAddressType.IPv4 && false == ipv6) {
        return addr;
      }
      if (addr.type == InternetAddressType.IPv6 && true == ipv6) {
        return addr;
      }
    }
  }
  return ipv6 ? InternetAddress.loopbackIPv6 : InternetAddress.loopbackIPv4;
}

debug(dynamic msg, {var error}) {
  dev.log(msg, error: error);
}
